Dynamic Templates¶
The previous tutorial covered how you can parameterise a template, allowing you to change variables on the fly.
While this works great for smaller scripts, there is more power to be leveraged here with “Dynamic” variables.
These allow you to “link” parameters together, so updating one value can make much larger changes to the script automatically.
Linked Parameters¶
Linking parameters together is simple to do.
When we covered kwargs
in the previous tutorial, we also described default
and value
.
These allow you to specify python code within them, which will be evaluated.
The syntax used here is based on the python f-string
syntax.
f-strings¶
f-strings are a feature in python that allows strings to be “evaluated” at runtime.
As a simple example, the string f"value={foo}"
will evaluate using the value of foo
. If we set foo=True
, print(f"value={foo}")
will return value=True
.
Note
In short, anything within {curly brackets}
will be evaluated as though it is python code.
Warning
Code within a dynamic value is executed via eval
on your local machine, so you should be aware of the security implications of this. Make sure that you’re aware of any code that may be running when you generate a jobscript!
Lets demonstrate this by setting up nodes
to be dependent on mpi
, omp
and a new cores_per_node
variable.
[2]:
from remotemanager import Computer
[3]:
template = """#!/bin/bash
#SBATCH --ntasks=#NTASKS#
#SBATCH --cpus-per-task=#CPUS_PER_TASK#
#SBATCH --nodes=#NODES:default={ntasks*cpus_per_task/cores_per_node}#
#SBATCH --queue=#QUEUE#
#SBATCH --account=#ACCOUNT#
#SBATCH --walltime=#TIME:format=time:default=3600#
#SBATCH --exclusive
# using cores_per_node: #CORES_PER_NODE:default=128#
#MODULES#"""
[4]:
test = Computer(template=template)
[5]:
test.ntasks = 1024
test.cpus_per_task = 4
print(test.script())
#!/bin/bash
#SBATCH --ntasks=1024
#SBATCH --cpus-per-task=4
#SBATCH --nodes=32
#SBATCH --walltime=01:00:00
#SBATCH --exclusive
# using cores_per_node: 128
Extra Variables¶
All variables that are required within the script must be available within the Computer itself.
The easiest way of doing this is to add a “commented” line to the template.
For example, we had to add cores_per_node
to the script, itself.
By doing this, you expose it as a value to the Computer, and add clarity for users.
Input Order¶
One thing to note with the previous example is that the order of your variables do not matter.
Since the values are evaluated when the script is generated, you can link values in any direction.
In this case, nodes
is dependent on the cores_per_node
value, which comes after it in the script.
Chaining Variables¶
Variables can also be “chained” into one another.
In this example, we are generating a default jobname that depends on the nodes. Nodes, in turn, depends on other variables.
[6]:
template = """#!/bin/bash
#SBATCH --ntasks=#NTASKS#
#SBATCH --cpus-per-task=#CPUS_PER_TASK#
#SBATCH --nodes=#NODES:default={ntasks*cpus_per_task/cores_per_node}#
#SBATCH --walltime=#TIME:format=time:default=3600#
#SBATCH --jobname=#JOBNAME:default=RUN_{ntasks}_{cpus_per_task}_{nodes}#
#SBATCH --exclusive
# using cores_per_node: #CORES_PER_NODE:default=128#
#MODULES#"""
[7]:
test = Computer(template=template)
[8]:
test.ntasks = 256
test.cpus_per_task = 4
print(test.script())
#!/bin/bash
#SBATCH --ntasks=256
#SBATCH --cpus-per-task=4
#SBATCH --nodes=8
#SBATCH --walltime=01:00:00
#SBATCH --jobname=RUN_256_4_8
#SBATCH --exclusive
# using cores_per_node: 128
Iterables¶
Added in version 0.13.5.
You are also able to specify limited iterables within your values, this can be useful for “selecting” arguments within jobscripts.
Similar to the quotation method of escaping control characters, values in {evaluation}
blocks are also escaped. This allows the specification of dictionaries.
Lets set up a template that requests a larger partition
if there are too many nodes.
[9]:
template = """#!/bin/bash
#SBATCH --ntasks=#NTASKS#
#SBATCH --cpus-per-task=#CPUS_PER_TASK#
#SBATCH --nodes=#NODES:default={ntasks*cpus_per_task/cores_per_node}#
#SBATCH --partition=#partition:default={partition_table[nodes<16]}#
# using cores_per_node: #CORES_PER_NODE:default=128#
#partition_table:default={{True: "small", False: "large"}}:hidden=True#
"""
test = Computer(template=template)
Note
We are making use of the hidden
variable here to reduce clutter.
Now if we generate a script that only requests 2 nodes, we would expect the “small” partition to be requested:
[10]:
print(test.script(ntasks=64, cpus_per_task=4))
#!/bin/bash
#SBATCH --ntasks=64
#SBATCH --cpus-per-task=4
#SBATCH --nodes=2
#SBATCH --partition=small
# using cores_per_node: 128
Now, if we generate a much larger job, it should request the “large” partition automatically
[11]:
print(test.script(ntasks=1024, cpus_per_task=4))
#!/bin/bash
#SBATCH --ntasks=1024
#SBATCH --cpus-per-task=4
#SBATCH --nodes=32
#SBATCH --partition=large
# using cores_per_node: 128
Note
This functionality is not limited to dicts. (Tested to be working with lists and tuples).
Dynamics and Escape Sequences¶
As mentioned previously, control characters like :
, =
can be escaped either by quoting, or by using the escape charater \
.
The resulting string will continue to function with dynamic values:
[12]:
template = """#!/bin/bash
#SBATCH --ntasks=#NTASKS#
#SBATCH --cpus-per-task=#CPUS_PER_TASK#
#SBATCH --nodes=#NODES:default={ntasks*cpus_per_task/cores_per_node}#
# using cores_per_node: #CORES_PER_NODE:default=128:hidden=True#
# tasks to nodes ratio: #ratio:default={ntasks}\\:{nodes}#
"""
test = Computer(template=template)
[13]:
print(test.script(ntasks=64, cpus_per_task=4))
#!/bin/bash
#SBATCH --ntasks=64
#SBATCH --cpus-per-task=4
#SBATCH --nodes=2
# tasks to nodes ratio: 64:2
Escaping Evaluation¶
Using the backslash escape technique is the only method for escaping the {}
evaluation pattern within values.
[14]:
template = r"""srcdir = #src:default=test#
dir = #dir:default=$\{HOME\}/{src}#"""
test = Computer(template=template)
[15]:
print(test.script())
srcdir = test
dir = ${HOME}/test
Try this without the escape \
, you’ll notice that the script production will fail, as it’s expecting a home
parameter.
Summary¶
Taking into account the previous two tutorials, you should now have all the tools needed to generate suitable jobscripts for any machine.
To summarise the steps:
Obtain a valid jobscript. This can be either from a job that you know has run (either yours or another user’s), the documentation, or the machine helpdesk.
Identify the parts of this script that you would like to have control over.
ntasks
,nodes
, etc.Convert the static parameters that the script has into sensible parameter names.
Load the template into a
Computer
Dataset
can now use this object to generate scripts based on the arguments that you give.
Tip
Remember that you can debug your script at any point manually by printing the output of the script()
method.